package org.lex.game.sudoku.view;

import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.util.Properties;

import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager.LookAndFeelInfo;

import org.jdesktop.application.ApplicationActionMap;
import org.lex.game.sudoku.Prefs;
import org.lex.game.sudoku.SudokuApplication;
import org.lex.game.sudoku.datatype.BoardStatus;
import org.lex.game.sudoku.datatype.HintType;
import org.lex.game.sudoku.datatype.SelectType;
import org.lex.game.sudoku.model.Point;
import org.lex.game.sudoku.model.builder.MicrosoftSudokuBuilder;
import org.lex.game.sudoku.model.builder.SudokuBuilder;
import org.lex.game.sudoku.util.Formatter.DurationFormat;
import org.lex.game.sudoku.view.newgame.NewGameDialog;
import org.lex.game.sudoku.view.solver.SolverObserverDialog;

public class ApplicationViewController extends AbstractController<ApplicationView> implements SudokuBoardListener,
		ItemListener {
	static final String NEW_GAME_ACTION = "applicationView.newGame";
	static final String RESTART_GAME_ACTION = "applicationView.restartGame";
	static final String AUTO_SOLVE_GAME_ACTION = "applicationView.autoSolveGame";
	static final String DUMP_GAME_ACTION = "applicationView.dumpGame";

	private SudokuBuilder sudokuBuilder = new MicrosoftSudokuBuilder();

	public ApplicationViewController(SudokuApplication application, ApplicationView view) {
		super(application, view);
		this.initialize();
	}

	// SudokuBoardListener
	@Override
	public void boardStatusChanged(SudokuBoard board, BoardStatus oldStatus, BoardStatus newStatus) {
		log.info("SudokuBoard status changed from " + oldStatus + " --> " + newStatus);
		if (oldStatus != newStatus) {
			switch (newStatus) {
			case SOLVED:
				this.gameSolved();
				break;
			case SOLVING:
				// this.newGame();
				break;
			default:
				log.error("SudokuBoard.Status[" + newStatus + "] UNKOWN!");
			}
		}
	}

	@Override
	public void cellSelected(SelectType type, int row, int column) {
		this.getView().getHistoryBoard().selectedCellChanged(type, row, column);
	}

	// ItemListener
	@Override
	public void itemStateChanged(ItemEvent e) {
		ApplicationView view = this.getView();
		if (ItemEvent.SELECTED == e.getStateChange()) {
			HintType type = (HintType) view.getHintsComboBox().getSelectedItem();
			log.debug("HintType [" + type + "] selected");
			switch (type) {
			case NONE:
				break;
			case CELL:
				break;
			case ALL:
				break;
			default:
				throw new IllegalArgumentException("HintType [" + type + "] unkown!");
			}
			view.getSudokuBoard().setHintType(type);
		}
		Point selectedCell = view.getSudokuBoard().getSelectedCell();
		if (null != selectedCell) {
			view.getHistoryBoard().selectedCellChanged(SelectType.SELECT, selectedCell.x, selectedCell.y);
		}
	}

	void store() {
		ApplicationView view = this.getView();
		Properties props = this.getPrefs().getConfig();
		// store hint type
		HintType hintType = (HintType) view.getHintsComboBox().getSelectedItem();
		log.debug("store HintType=" + hintType);
		props.setProperty(Prefs.KEY_SUDOKU_HINT, hintType.name());
		// store look&feel
		LookAndFeelInfo lnf = view.getLookAndFeelComboBox().getSelectedLookAndFeel();
		log.debug("store look&feel=" + lnf.getName());
		props.setProperty(Prefs.KEY_LNF, lnf.getClassName());
	}

	void restore() {
		Properties props = this.getPrefs().getConfig();
		// restore hint type
		String hintTypeName = props.getProperty(Prefs.KEY_SUDOKU_HINT);
		HintType hintType = HintType.getByName(hintTypeName);
		log.debug("restore HintType=" + hintType);
		this.getView().getHintsComboBox().setSelectedItem(hintType);
	}

	private void newGame() {
		log.info("start new game");
		SudokuBuilder builder = NewGameDialog.getSudokuBuilder(getApplication(), this.sudokuBuilder);
		if (null != builder) {
			this.sudokuBuilder = builder;
			ApplicationView view = this.getView();
			SudokuBoard board = view.getSudokuBoard();
			board.setStatus(BoardStatus.SOLVING);
			board.getSudokuModel().newGame(this.sudokuBuilder);
			board.setStartTime(System.currentTimeMillis());
			view.getTimeCountDown().start();
		}
	}

	private void gameSolved() {
		this.getView().getTimeCountDown().stop();
		SudokuBoard board = this.getView().getSudokuBoard();
		long timeUsage = System.currentTimeMillis() - board.getStartTime();
		JOptionPane.showMessageDialog(this.getMainFrame(),
				"Game solved using " + DurationFormat.SHORT.format(timeUsage) + " seconds, "
						+ board.getSudokuModel().getOriginalEmptyCellsCount() + " numbers were filled.", "Congratulations!",
				JOptionPane.INFORMATION_MESSAGE);
	}

	private void restartGame() {
		int ret = JOptionPane.showConfirmDialog(this.getMainFrame(),
				"ALL cell will be cleared, are you sure to restart game?", "Restart Game Confirm",
				JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
		if (JOptionPane.YES_OPTION == ret) {
			ApplicationView view = this.getView();
			SudokuBoard board = view.getSudokuBoard();
			board.setStatus(BoardStatus.SOLVING);
			board.getSudokuModel().reset();
			board.setStartTime(System.currentTimeMillis());
			view.getTimeCountDown().start();
		}
	}

	private void autoSolve() {
		JFrame mainFrame = getMainFrame();
		final SolverObserverDialog d = new SolverObserverDialog(mainFrame, this.getView().getSudokuBoard()
				.getSudokuModel());
		d.setLocationRelativeTo(mainFrame);
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				d.setVisible(true);
			}
		});
	}

	private void dump() {
		DumpDialog d = new DumpDialog(this.getMainFrame(), this.getView().getSudokuBoard().getSudokuModel());
		d.setLocationRelativeTo(this.getMainFrame());
		d.setVisible(true);
	}

	private void initialize() {
		ApplicationActionMap am = this.getActionMap();
		am.put(NEW_GAME_ACTION, new NewGameAction());
		am.put(RESTART_GAME_ACTION, new RestartGameAction());
		am.put(AUTO_SOLVE_GAME_ACTION, new SolveGameAction());
		am.put(DUMP_GAME_ACTION, new DumpAction());
	}

	private class NewGameAction extends AbstractAction {
		private static final long serialVersionUID = 8760289452057725134L;

		public NewGameAction() {
			super("New Game");
			this.putValue(MNEMONIC_KEY, KeyEvent.VK_N);
			this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK));
		}

		public void actionPerformed(ActionEvent e) {
			newGame();
		}
	}

	private class RestartGameAction extends AbstractAction {
		private static final long serialVersionUID = -8436372913310311892L;

		public RestartGameAction() {
			super("Restart Game");
		}

		public void actionPerformed(ActionEvent e) {
			restartGame();
		}
	}

	private class SolveGameAction extends AbstractAction {
		private static final long serialVersionUID = 3535302838676720620L;

		public SolveGameAction() {
			super("Solve");
		}

		public void actionPerformed(ActionEvent e) {
			autoSolve();
		}
	}

	private class DumpAction extends AbstractAction {
		private static final long serialVersionUID = 5648952864156516342L;

		public DumpAction() {
			super("dump");
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			dump();
		}

	}
}
